Shell 格式化输出和解析
打印消息
echo "Hello, world!"
# or
printf "%-5s %-10s %-4s\n" No Name Mark
printf "%-5s %-10s %-4.2f\n" 1 Sarath 80.3456
printf "%-5s %-10s %-4.2f\n" 2 James 90.9989
printf "%-5s %-10s %-4.2f\n" 3 Jeff 77.564
可以得到以下格式化输出
Hello, world!
No Name Mark
1 Sarath 80.35
2 James 91.00
3 Jeff 77.56
提示
Go 也提供了类似的工具,具体看 Go 官方包的文本对齐工具 tabwriter 那篇笔记
sort 对输入进行排序
sort 命令是在 Linux 里非常有用,它将文件进行排序,并将排序结果标准输出。sort 命令既可以从特定的文件,也可以从 stdin 中获取输入。
sort 将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按 ASCII 码值进行比较,最后将他们按升序输出。
$ cat netstat.txt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 alsritter-PC:35984 public1.alidns.co:https ESTABLISHED
tcp 0 0 localhost:44891 localhost:46338 TIME_WAIT
tcp 0 0 localhost:60202 localhost:30136 ESTABLISHED
tcp 0 0 localhost:44891 localhost:46660 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46636 TIME_WAIT
tcp 0 0 localhost:33279 localhost:53712 ESTABLISHED
tcp 0 0 localhost:44891 localhost:46540 TIME_WAIT
tcp 0 0 localhost:44891 localhost:45958 TIME_WAIT
$ sort netstat.txt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 alsritter-PC:35984 public1.alidns.co:https ESTABLISHED
tcp 0 0 localhost:33279 localhost:53712 ESTABLISHED
tcp 0 0 localhost:44891 localhost:45958 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46338 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46540 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46636 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46660 TIME_WAIT
tcp 0 0 localhost:60202 localhost:30136 ESTABLISHED
sort 默认的排序方式是升序,如果想改成降序,就加个 -r
就搞定了。
uniq 删除文件中重复的行
$ cat testfile #原有内容
test 30
test 30
test 30
Hello 95
Hello 95
Hello 95
Hello 95
Linux 85
Linux 85
$ uniq testfile #删除重复行后的内容
test 30
Hello 95
Linux 85
awk 命令
awk 的核心是格式化。
一切都将从一个 awk 关键字开始,然后是一个 awk 用单引号括起来的程序。最后是该 awk 命令将处理的文件的名称。
# 只执行一个动作
awk '{action}' 文件名
# 仅根据模式搜索文本
awk '模式' 文件名
# 结合模式和动作的使用
awk '模式 {action}' 文件名
以下举例,这里只介绍常用的操作
从 netstat 命令中提取了如下信息作为用例:
$ cat netstat.txt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 alsritter-PC:35984 public1.alidns.co:https ESTABLISHED
tcp 0 0 localhost:44891 localhost:46338 TIME_WAIT
tcp 0 0 localhost:60202 localhost:30136 ESTABLISHED
tcp 0 0 localhost:44891 localhost:46660 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46636 TIME_WAIT
tcp 0 0 localhost:33279 localhost:53712 ESTABLISHED
tcp 0 0 localhost:44891 localhost:46540 TIME_WAIT
tcp 0 0 localhost:44891 localhost:45958 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46162 TIME_WAIT
tcp 0 0 localhost:38144 localhost:33053 ESTABLISHED
tcp 0 0 localhost:44891 localhost:46518 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46444 TIME_WAIT
awk 内置的变量
其中的 $1..$n
表示第几例。注:$0
表示整个行。
$ awk '{print $1, $4}' netstat.txt
Active (w/o
Proto Local
tcp alsritter-PC:35984
tcp localhost:44891
tcp localhost:60202
tcp localhost:44891
tcp localhost:44891
tcp localhost:33279
....
awk 的一些内置的变量:
变量名 | 作用 |
---|---|
$0 | 当前记录(这个变量中存放着整个行的内容) |
$1~$n | 当前记录的第n个字段,字段间由FS分隔 |
FS | 输入字段分隔符 默认是空格或Tab |
NF | 当前记录中的字段个数,就是有多少列 |
NR | 已经读出的记录数,就是行号,从1开始,如果有多个文件话,这个值也是不断累加中。 |
FNR | 当前记录数,与NR不同的是,这个值会是各个文件自己的行号 |
RS | 输入的记录分隔符, 默认为换行符 |
OFS | 输出字段分隔符, 默认也是空格 |
ORS | 输出的记录分隔符,默认为换行符 |
FILENAME | 当前输入文件的名字 |
比如:我们如果要输出行号
$ awk '$3==0 && $6=="ESTABLISHED" || NR==1 {printf "%02s %s %-20s %-20s %s\n",NR, FNR, $4,$5,$6}' netstat.txt
1 1 (w/o servers)
3 3 alsritter-PC:35984 public1.alidns.co:https ESTABLISHED
5 5 localhost:60202 localhost:30136 ESTABLISHED
8 8 localhost:33279 localhost:53712 ESTABLISHED
12 12 localhost:38144 localhost:33053 ESTABLISHED
....
还有一些常用逻辑符号
~ 匹配,与==相比不是精确比较
!~ 不匹配,不精确比较
== 等于,必须全部相等,精确比较
!= 不等于,精确比较
&& 逻辑与
|| 逻辑或
awk 换行符
注意,默认的分隔符是空格,如果需要修改分隔符可以使用 -F
,例如使用冒号当分隔符
$ awk -F: '{print $1, $4}' netstat.txt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 alsritter-PC
tcp 0 0 localhost
tcp 0 0 localhost
tcp 0 0 localhost
....
如果你要指定多个分隔符
$ awk -F '[;:]'
awk 格式化
再来看看 awk 的格式化输出,和C语言的 printf 没什么两样:
$ awk '{printf "%-8s %-8s %-8s %-18s %-22s %-15s\n",$1,$2,$3,$4,$5,$6}' netstat.txt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign
tcp 0 0 alsritter-PC:35984 public1.alidns.co:https ESTABLISHED
tcp 0 0 localhost:44891 localhost:46338 TIME_WAIT
tcp 0 0 localhost:60202 localhost:30136 ESTABLISHED
tcp 0 0 localhost:44891 localhost:46660 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46636 TIME_WAIT
....
awk 正则匹配
$ awk '$6 ~ /FIN/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
1 Local-Address Foreign-Address State
6 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2
9 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2
13 coolshell.cn:80 124.152.181.209:26825 FIN_WAIT1
18 coolshell.cn:80 117.136.20.85:50025 FIN_WAIT2
$ awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
1 Local-Address Foreign-Address State
5 coolshell.cn:80 124.205.5.146:18245 TIME_WAIT
6 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2
9 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2
11 coolshell.cn:80 183.60.215.36:36970 TIME_WAIT
13 coolshell.cn:80 124.152.181.209:26825 FIN_WAIT1
15 coolshell.cn:80 183.60.212.163:51082 TIME_WAIT
18 coolshell.cn:80 117.136.20.85:50025 FIN_WAIT2
上面的第一个示例匹配 FIN 状态, 第二个示例匹配 WAIT 字样的状态。~
表示模式开始。/ /
中是模式。这就是一个正则表达式的匹配。
可以使用 /FIN|TIME/
来匹配 FIN 或者 TIME :
$ awk '$6 ~ /FIN|TIME/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
1 Local-Address Foreign-Address State
5 coolshell.cn:80 124.205.5.146:18245 TIME_WAIT
6 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2
9 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2
11 coolshell.cn:80 183.60.215.36:36970 TIME_WAIT
13 coolshell.cn:80 124.152.181.209:26825 FIN_WAIT1
15 coolshell.cn:80 183.60.212.163:51082 TIME_WAIT
模式取反的例子:
$ awk '$6 !~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
# or
$ awk '!/WAIT/' netstat.txt
sed 命令
sed 的核心是正则
TODO: ...